package com.bob.clan.wxconnector.controller;

import com.bob.clan.wxconnector.DTO.WebAuthorMsgDTO;
import com.bob.clan.wxconnector.component.MpComponent;
import com.bob.clan.wxconnector.component.WebAuthorComponent;
import com.bob.clan.wxconnector.entity.Fans;
import com.bob.clan.wxconnector.entity.Mp;
import com.bob.clan.wxconnector.exception.LoginException;
import com.bob.clan.wxconnector.exception.MpException;
import com.bob.clan.wxconnector.service.IFansService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.security.MessageDigest;
import java.util.Hashtable;
import java.util.Set;

/**
 * 页面授权登录，与扫码登录不同
 */
@Slf4j
@Controller
@RequestMapping("/commonAuthor")
public class CommonAuthorizeController {
    @Autowired
    private MpComponent mpComponent;
    @Autowired
    private WebAuthorComponent webAuthorComponent;
    @Autowired
    private IFansService fansService;

    private static Integer lock_flag = 0;

    private Hashtable<String, Long> codeMap = new Hashtable<String, Long>();

    /**
     * 初始化页面授权的信息，通用参数规则定义了本业务的接口标准：
     * 原理：从业务系统传入的参数将在HTML页的store里存放，待获取用户的微信信息后，按该参数返回到业务系统
     * 具体参数如下：
     * 1、sysCode：业务系统编码
     * 2、reCallUrl：回调地址
     * 3、recallParams：回调地址传的参数
     * 4、purposes：业务系统的业务说明，用于本页中告之当前用户
     *
     * 注：该页的微信鉴权中心的userInfoURL
     * @return
     */
    @RequestMapping("/init")
    public String initAuthor(String authorType, String sysCode, String recallUrl, String recallParams, String purposes, Model model) {
        //0、保存业务系统需要回调的地址与参数
        model.addAttribute("recallUrl", recallUrl);
        model.addAttribute("recallParams", recallParams);
        //1、生成需客户授权获取用户微信信息的鉴权中心地址
        Mp mp = mpComponent.getBySysCode(sysCode);
        model.addAttribute("mp", mp);
        model.addAttribute("businessPurposes", purposes);
        if(authorType.equals("userInfo")){
            String authorUrl = webAuthorComponent.getCommonAuthorUserInfoUrl(mp);
            model.addAttribute("authorUrl", authorUrl);
            log.info("初始化页面授权已完成，用户点授权后将调用地址："+authorUrl);
            return "commonAuthor/initUserInfo";
        }else{
            String authorUrl = webAuthorComponent.getCommonAuthorUserBaseUrl(mp);
            model.addAttribute("authorUrl", authorUrl);
            log.info("初始化页面授权已完成，直接使用静默授权："+authorUrl);
            return "commonAuthor/initUserBase";
        }
    }



    /**
     * 本处是initAuthor方法指定的回调地址，见配置文件
     * @return
     */
    @RequestMapping("/doAuthor")
    public String doAuthor(String params, String code, String state, Model model) {
        log.info("完成页面授权commonAuthor UserInfo，请求参数：----params----" + params + "----code----" + code + "-----state-----" + state);
        try {
            if (this.isValidCode(code)) {
                Mp mp = mpComponent.getBySysCode(params);
                log.info("code获取openId方法将要执行（getBaseOpenId）");
                WebAuthorMsgDTO userBaseMsg = webAuthorComponent.getUserBaseMsg(mp, code);
                String openid = userBaseMsg.getOpenid();
                Fans fans = fansService.getFans(openid);
                if (fans == null) {
                    fans = webAuthorComponent.getFansInfo(userBaseMsg.getAccess_token(), openid);
                    fansService.save(fans);
                    log.info("新用户，已存档");
                }
                log.debug("页面授权成功，即将回到业务系统中");
                model.addAttribute("mp", mp);
                model.addAttribute("fans", fans);
                String timestamp = String.valueOf(System.currentTimeMillis());
                model.addAttribute("timestamp", timestamp);
                String signature = getSHA256(fans, timestamp, mp);
                model.addAttribute("signature", signature);
                return "commonAuthor/autoRecallUrl";
            } else {
                log.error(code + ",the code had used, wechat sb bug review!");
                model.addAttribute("error", new LoginException(4001));
                return "error";
            }
        } catch (MpException mpException) {
            log.error(mpException.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        model.addAttribute("error", new LoginException(4002));
        return "error";
    }

    /**
     * 登录中心与业务系统的登录信息加官，业务系统收到签名后，可验证登录信息源是登录中心，否则可以阻止非法登录
     *
     * @param fans
     * @param timestamp
     * @return
     */
    private String getSHA256(Fans fans, String timestamp, Mp mp) {
        String input = fans.getOpenid() + timestamp + mp.getSysCode();
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] messageDigest = md.digest(input.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : messageDigest) {
                hexString.append(String.format("%02x", b));
            }
            return hexString.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //因企鹅平台的bug,短时间内可能发送的code是已使用的，此时将导致本次code因重复而无效；
    private boolean isValidCode(String code) {
        if (codeMap.get(code) == null) {
            log.debug("new code," + codeMap.size() + ",hashCode:");
            codeMap.put(code, System.currentTimeMillis());
            synchronized (CommonAuthorizeController.lock_flag) {
                if (codeMap.size() > 200) {
                    flushCodeMap(5);
                }
            }
            return true;
        } else {
            long codeTime = codeMap.get(code);
            long ms = System.currentTimeMillis() - codeTime;
            log.debug("old code, had user at " + ms + "ms ");
            return false;
        }
    }

    //防内存泄露，定期清理静态数据
    private void flushCodeMap(int keepSecends) {
        Long curTime = System.currentTimeMillis();
        Hashtable<String, Long> newCodeMap = new Hashtable<String, Long>();
        Set<String> codeKey = codeMap.keySet();
        log.debug("snsCodeMap size:" + codeMap.size() + ",will do flush! hashCode:" + codeMap.hashCode());
        for (String code : codeKey) {
            Long codeTime = codeMap.get(code);
            log.debug("code=" + code + ",curTime=" + curTime + ",codeTime=" + codeTime);
            if (curTime - codeTime < keepSecends * 1000) {
                newCodeMap.put(code, codeTime);
            }
        }
        codeMap = newCodeMap;
        log.debug("flush done, snsCodeMap size:" + codeMap.size() + ",hashCode:" + codeMap.hashCode());
    }

    private String getParamsFromState(String params, int type) {
        String[] paramsArray = params.split("_");
        //业务系统编码
        String busSysCode = paramsArray[0];
        //websocket系统SessionId，用于标识登录者
        String wsSessionId = paramsArray[1];
        return paramsArray[type];
    }
    //对参数进行处理

}
